#!/usr/bin/env python
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-

"""Integration of package managers into SystemUpdater"""

import os
import json
import logging
import subprocess
import traceback
import dbus
import time
import threading
import fcntl
import shutil
from apt import Cache
from gettext import gettext as _
from SystemUpdater.Core.errors import *
from SystemUpdater.Core.enums import *
from SystemUpdater.Core.PluginManager import *
from SystemUpdater.Core.DataAcquisition import get_east_8_time
from SystemUpdater.Core.utils import get_config_patch
from SystemUpdater.Core.JsonConfigParser import JsonConfig as json_config
from aptdaemon.enums import ERROR_PACKAGE_DOWNLOAD_FAILED
class NowUpgradeMeta:
    """
    Represent the (potentially partial) results of an unattended-upgrades
    run
    """
    def __init__(self,_upgrade_groups = [],_single_pkgs = []):
        self.push_content = []
        self.upgrade_groups = _upgrade_groups
        self.single_pkgs = _single_pkgs
    
    def get_content(self):
        return self.single_pkgs + self.upgrade_groups

class UpdateEssentialItem():
    def __init__(self,parent):
        self.essential_pkgs = []
        self.remove_white = []

        self.read_path = get_config_patch() + 'kylin-update-desktop-system.json'
        self.update()

    def update(self):
        if os.path.exists(self.read_path):
            with open(self.read_path,'r') as f:
                try:
                    data = json.load(f)
                    self.essential_pkgs = data['install_list']
                except Exception as exc:
                    logging.warning(exc)
                try:
                    self.remove_white = data['remove_white_list']
                except Exception as exc:
                    pass
                    # logging.warning(exc)
    
    def check_essential(self,remove_pkgs):
        logging.info("Check: For remove of Essential Packages...")
        for pkg in remove_pkgs:
            if pkg in self.essential_pkgs:
                logging.error("Essential Packages(%s) will be Removed...",pkg)
                raise UpdateBaseError(ERROR_REMOVE_ESSENTIAL_PACKAGES)
    
    def check_white(self,remove_pkgs):
        logging.info("Check: remove pkg in White Packages...")
        new_upgrade_list = list(set(remove_pkgs) & set(self.remove_white))

        for pkg in new_upgrade_list:
            if pkg in remove_pkgs:
                logging.info("%s will be remove in remove pkg...",pkg)
                remove_pkgs.remove(pkg)

class AptdBaseInformation:
    def __init__(self,
                progress = 0,
                status='',
                details='',
                cancelable = False,
                error_details = '',
                current_items = 1,
                total_items = 1,
                on_installing = False,
                ):

        self._progress = progress
        self.status = status
        self.details = details
        self.cancelable = cancelable
        self.error_details = error_details
        self.current_items = current_items
        self.total_items = total_items
        self.on_installing = on_installing

        self.start_progress = 0
        self.end_progress = 100

    def _get_progress(self):
        return self._progress

    def _set_progress(self, progress):
        if self.current_items == 0 and \
                self.total_items == 0 and self.on_installing == False:
            return

        if self.start_progress == 0 and self.end_progress == 100:
            if progress > self._progress:
                self._progress = progress
            else:
                return
        else:
            progress = int(self.start_progress + (progress / 100) *
                        (self.end_progress - self.start_progress))
            self._progress = progress

    progress = property(_get_progress, _set_progress,
                    doc="The required disk space of the transaction.")

class InstallBackend():
    INSTALL_OUTPUT_JSON = "/var/lib/kylin-system-updater/json/requires_upgrade_meta.json"
    POWEROFF_INSTALL_JSON = "/var/lib/kylin-system-updater/json/poweroff_install_meta.json"
    OUTPUT_JSON_PATH = '/var/lib/kylin-system-updater/json/'

    ACTION_DEFUALT_STATUS = -1
    ACTION_UPDATE = 0
    ACTION_INSTALL = 1
    ACTION_INSTALL_DEB = 2
    ACTION_CHECK_RESOLVER = 3
    ACTION_DOWNLOADONLY = 4
    ACTION_FIX_BROKEN = 5
    ACTION_REMOVE_PACKAGES = 6
    ACTION_FIX_INCOMPLETE = 7
    ACTION_CLEAN = 8
    ACTION_INSTALL_SHUTDOWN = 9
    ACTION_BACKGROUND_UPGRADE = 10
    ACTION_CHECK_BROKEN = 11
    ACTION_INSTALL_DEB_RESOLVER = 12
    ACTION_SLEF_UPGRADE = 13

    MODE_DEFAULT_STATUS = -1
    
    MODE_INSTALL_PARTIAL = 0
    MODE_INSTALL_ALL = 1
    MODE_INSTALL_SYSTEM = 2

    MODE_UPDATE_CACHE = 0
    MODE_UPDATE_ALL = 1

    MODE_SHUTDOWN_BY_DBUS = 0
    MODE_SHUTDOWN_BY_USER = 1

    def __init__(self, window_main, action,mode = MODE_DEFAULT_STATUS):
        self.window_main = window_main
        self.bus = window_main.bus
        self.cache = window_main.cache
        self.action = action
        self.action_mode = mode

        self.window_main.now_working = action

        self.aptd_base = AptdBaseInformation()
        self.collector = window_main.collector
        self.configs_cover = self.window_main.configs_cover

        self.backend_data = window_main.local_meta
        self.dbus_send = window_main.dbusController
        self.database = window_main.sqlite3_server
        self.delay_shutdown = window_main.delay_shutdown
        self.inhibit_shutdown = InhibitShutdownLock()
        self.now_upgrades = NowUpgradeMeta()

        if self.action != self.ACTION_UPDATE:
            self.update_essential = UpdateEssentialItem(self)
            self.scripts = pluginManagerClass()
        
    def start(self,push_content = []):
        try:
            self.now_upgrades.push_content = push_content

            if self.action == self.ACTION_SLEF_UPGRADE:
                pkgs_install,pkgs_upgrade,pkgs_remove = self.get_mark_from_cache(self.cache)
                self.now_upgrades.single_pkgs = push_content

                logging.info("SELF-UPGRADE install:%d , upgrade:%d remove:%d",len(pkgs_install),len(pkgs_upgrade),len(pkgs_remove))
                
                self.update_essential.check_essential(pkgs_remove)
                self.commit(self.action,pkgs_install, pkgs_upgrade, pkgs_remove)
            elif self.action == self.ACTION_INSTALL or self.action == self.ACTION_DOWNLOADONLY:
                if self.action_mode == self.MODE_INSTALL_SYSTEM:
                    self.now_upgrades.upgrade_groups = ["dist-upgrade"]
                    pkgs_install,pkgs_upgrade,pkgs_remove = self.backend_data.get_resolver_upgrade()
                else:
                    self.now_upgrades.single_pkgs,self.now_upgrades.upgrade_groups = self.backend_data.classify_content(push_content)
                    pkgs_install,pkgs_upgrade,pkgs_remove = self.backend_data.get_resolver_upgrade()

                logging.info("INSTALL install:%d , upgrade:%d remove:%d",len(pkgs_install),len(pkgs_upgrade),len(pkgs_remove))

                self.update_essential.check_essential(pkgs_remove)

                if self.backend_data.lack_of_space == True:
                    raise UpdateBaseError(ERROR_NOT_DISK_SPACE)

                if self.action != self.ACTION_DOWNLOADONLY and self.configs_cover.getWithDefault("TestsConfig","check_device_power",str(True)):
                    self.dbus_send.check_device_power()

                if len(pkgs_install) == 0 and len(pkgs_upgrade) == 0:
                    raise UpdateBaseError(ERROR_NOT_UPGRADE_PACKAGES)

                for ul in self.collector.upgrade_list:
                    self.collector.Upgrade_Process_Msg(self.action, {"appname":ul})

                self._update_to_config(pkgs_install,pkgs_upgrade,pkgs_remove)

                self.commit_download(self.action,pkgs_install, pkgs_upgrade, pkgs_remove)
            elif self.action == self.ACTION_INSTALL_SHUTDOWN:
                self.now_upgrades.single_pkgs,self.now_upgrades.upgrade_groups = self.backend_data.classify_content(push_content)
                if self.action_mode == self.MODE_SHUTDOWN_BY_USER:
                    self.start_plymouth_splash()
                    self._stop_lightdm_service()
                    self._switch_to_tty1()
                    for i in range(3):
                        self._restart_logind_service()
                        time.sleep(2)
                    self.delay_shutdown.unlock()
                    self._switch_to_tty1()
                else:
                    self.start_plymouth_splash()
                self.check_plymouth_state()
                logging.info("The task Queue(%r) to be upgraded...",self.backend_data.steps_queue)
                logging.info("Final State(%s) of the device after installation is completed...",self.backend_data.finished_status)
                self._start_install_queue()
            elif self.action == self.ACTION_CHECK_BROKEN:
                try:
                    self.cache.fix_broken()
                except Exception as e:
                    logging.error(e)
                    raise UpdateBaseError(ERROR_RESOLVER_FAILED)

                pkgs_install,pkgs_upgrade,pkgs_remove = self._get_pkgs_from_cache(self.cache)
                logging.warning("Fix broken Packages is need to install:%a and upgrade:%a and remove:%a",pkgs_install,pkgs_upgrade,pkgs_remove)

                if len(pkgs_remove) == 0:
                    logging.warning("start fix system broken pkgs...")
                    self.fix_broken()
                else:
                    delete_des,raw_des= self._supple_description(self.cache,pkgs_remove)                    
                    self.dbus_send.UpdateFixBrokenStatus(True,True,pkgs_remove,raw_des,delete_des,'','')
                    self._action_done(self.action,False,True,'','')
                    
            elif self.action == self.ACTION_FIX_BROKEN:
                pkgs_install,pkgs_upgrade,pkgs_remove = self._get_pkgs_from_cache(self.cache)

                self.update_essential.check_essential(pkgs_remove)
                self.fix_broken()
            elif self.action == self.ACTION_FIX_INCOMPLETE:
                self.fix_incomplete()
            elif self.action == self.ACTION_REMOVE_PACKAGES:
                self._start_install_lock(_("Kylin System Updater"))
                self.purge_packages(push_content)
            elif self.action == self.ACTION_CLEAN:
                self.clean()
            elif self.action == self.ACTION_UPDATE:
                self.update()
        except UpdateBaseError as excep:
            self._action_done(self.action,True,False,excep.header,excep.desc,excep.code)
        except UpdateProgressExit as excep:
            pass
        except Exception as e:
            logging.error(e) 
            traceback.print_exc()

    def _make_steps_resolver(self,calcul_data):
        steps_reboot = False
        total_remove = []
        for step_name in calcul_data.get_steps():
            step_install,step_upgrade = calcul_data.get_steps_pkgs(step_name)

            step_install,step_upgrade,step_remove = self.cache.make_problem_resolver(step_install,step_upgrade)

            logging.info("RESOLVER(%s):Steps install:%d , upgrade:%d remove:%d ",step_name,len(step_install),\
                                                                            len(step_upgrade),len(step_remove))

            if len(step_install) == 0 and len(step_upgrade) == 0 and len(step_remove) == 0:
                logging.warning("Steps(%s) This update cannot detect the upgradeable package.",step_name)
                continue

            calcul_data.save_steps_resolver(step_name,step_install,step_upgrade,step_remove)

            total_remove += step_remove

            calcul_data.steps_queue.insert(0,step_name)

            steps_reboot = calcul_data.check_steps_reboot(step_name)
            if steps_reboot == True and calcul_data.need_poweroff_install != True:
                break
        
        return total_remove,steps_reboot

    def start_resolver(self,push_content):
        try:
            total_remove = []
            is_reboot = False
            self.backend_data.reset_task_queue()
            
            if self.action_mode != self.MODE_INSTALL_SYSTEM:
                all_install,all_upgrade = self.backend_data.get_push_pkgs(push_content)

                pkgs_install,pkgs_upgrade,pkgs_remove = self.cache.make_problem_resolver(all_install,all_upgrade)
                total_remove += pkgs_remove

                self.backend_data.save_groups_resolver(pkgs_install,pkgs_upgrade,pkgs_remove,getattr(self.cache, "additional_required_space",0))
                
                self.backend_data.lack_of_space = self.cache.check_free_space()

                if self.backend_data.is_steps(push_content) == True:
                    step_remove,is_reboot = self._make_steps_resolver(self.backend_data)
                    total_remove += step_remove
            else:
                pkgs_install,pkgs_upgrade,pkgs_remove = self.cache.updateDistUpgrade()

                self.backend_data.save_groups_resolver(pkgs_install,pkgs_upgrade,pkgs_remove,getattr(self.cache, "additional_required_space",0))
                self.backend_data.lack_of_space = self.cache.check_free_space()
                total_remove += pkgs_remove

            if self.backend_data.need_poweroff_install == True or is_reboot == False:
                self.backend_data.steps_queue.insert(0,self.backend_data.resolver_name)

            total_remove = list(set(total_remove))

            logging.warning("ProblemResolver of the deletion package list:%s",str(total_remove))                
            self.update_essential.check_white(total_remove)
            
            logging.info("RESOLVER:ALL install:%d , upgrade:%d remove:%d ",len(pkgs_install),len(pkgs_upgrade),\
                                                                        len(total_remove))
            
            is_remove_pkgs = len(total_remove) != 0

            # 数据上报
            self.collector.Generate_Msg(self.now_upgrades.get_content(), self.action_mode)
            errorCode = ""
            if is_remove_pkgs:
                errorCode = _("Need remove pkgs: ")+", ".join(total_remove)
            for ul in self.collector.upgrade_list:
                self.collector.Upgrade_Process_Msg(self.action, {"appname":ul, "status":is_remove_pkgs, "errorCode":errorCode})
            
            delete_des,raw_des= self._supple_description(self.cache,total_remove)

            if self.action_mode != self.MODE_INSTALL_SYSTEM:
                self.dbus_send.UpdateDependResloveStatus(True,is_remove_pkgs,total_remove,raw_des,delete_des,'','')
            else:
                self.dbus_send.DistupgradeDependResloveStatus(True,is_remove_pkgs,total_remove,raw_des,delete_des,'','')

            self._action_done(self.action,False,True,'','')
            
            if is_remove_pkgs:
                threading_emulate = threading.Thread(target=self._emulate_calcul_delete,args=((pkgs_upgrade + pkgs_install),))
                threading_emulate.start()
        except UpdateBaseError as excep:
            self._action_done(self.action,True,False,excep.header,excep.desc,excep.code)
        except UpdateProgressExit as excep:
            pass
        except Exception as e:
            self.backend_data.reset_task_queue()
            logging.error(e) 
            traceback.print_exc()

    def _supple_description(self,cache,remove_pkgs):
        delete_desc = []
        raw_description = []
        for pkg in remove_pkgs:
            pkg_obj = cache[pkg]
            raw_description.append(getattr(pkg_obj.candidate, "summary", ''))
            delete_desc.append('')

        return delete_desc,raw_description
        
    def start_alone(self,push_content = [],_is_install = False, caller=''):
        # 安装本地deb包的接口
        if self.action == self.ACTION_INSTALL_DEB: 
            try:
                self._start_install_lock(caller=caller)
                self.install_deb(install_path = push_content, install_force = _is_install)
            except Exception as e:
                logging.error(str(e))
        elif self.action == self.ACTION_INSTALL_DEB_RESOLVER:
            try:
                pkgs_install,pkgs_upgrade,pkgs_remove = self.cache.make_problem_resolver(push_content)
                self._start_install_lock(caller=caller)
                self.commit(self.ACTION_INSTALL,pkgs_install,pkgs_upgrade,pkgs_remove)
            except Exception as e:
                logging.error(str(e))
        # 安装在线包的接口
        elif self.action == self.ACTION_BACKGROUND_UPGRADE:
            try:
                pkgs_install = [ str(pkg) for pkg in push_content]
                logging.info("Install deb package, open cache")
                cache = Cache()
                for pkg_name in pkgs_install:
                    if pkg_name not in cache:
                        if "=" not in pkg_name or ("=" in pkg_name and pkg_name.split("=")[0] not in cache):
                        # 没找到包或格式不正确
                            self.dbus_send.UpdateInstallFinishedNext(False, pkgs_install,'', "'"+pkg_name+"' is not in cache", "")
                            return
                self._start_install_lock(caller=caller)
                self.commit(self.ACTION_INSTALL,pkgs_install,[],[])
            except Exception as e:
                logging.error(str(e))
                self.dbus_send.UpdateInstallFinishedNext(False, pkgs_install,'',str(e), "")

    def update(self):
        """Run a update to refresh the package list"""
        raise NotImplementedError

    def clean(self):
        """Remove all downloaded files.t"""
        raise NotImplementedError

    def commit(self,model,pkgs_install, pkgs_upgrade, pkgs_remove,pkgs_downgrade = []):
        """Commit the cache changes """
        raise NotImplementedError

    def install_deb(self,install_path,install_force):
        """install_deb the cache changes """
        raise NotImplementedError

    def fix_broken(self):
        """fix_broken_depends"""
        raise NotImplementedError

    def fix_incomplete(self):
        """fix_incomplete_install"""
        raise NotImplementedError

    def purge_packages(self,pkgs_purge):
        """purge_packages"""
        raise NotImplementedError

    def _get_pkgs_from_cache(self,cache):
        pkgs_install = []
        pkgs_upgrade = []
        pkgs_remove = []
        for pkg in cache:
            try:
                if pkg.marked_install :
                    pkgs_install.append(pkg.name)
                elif pkg.marked_upgrade:
                    pkgs_upgrade.append(pkg.name)
                elif pkg.marked_delete:
                    pkgs_remove.append(pkg.name)
            except KeyError:
                # pkg missing from fresh_cache can't be modified
                pass
        return pkgs_install,pkgs_upgrade,pkgs_remove

    def get_mark_from_cache(self,cache):
        pkgs_install = []
        pkgs_upgrade = []
        pkgs_remove = []

        for pkg in cache:
            try:
                if pkg.marked_install:
                    pkgname = pkg.name
                    if pkg.is_auto_installed:
                        pkgname += "#auto"
                        pkgs_install.append(pkgname)
                    else:
                        pkgs_install.append(pkgname)
                elif pkg.marked_upgrade:
                        pkgs_upgrade.append(pkg.name)
                elif pkg.marked_delete:
                    pkgs_remove.append(pkg.name)
            except KeyError:
                pass
        return pkgs_install,pkgs_upgrade,pkgs_remove

    def _make_pkgs_list(self,cache,groups_pkgs,groups_list,pkg_list):
        pkgs_install = []
        pkgs_upgrade = []
        
        try:
            #单包的升级方式
            for pkg in pkg_list:
                if cache[pkg].is_installed:
                    pkgs_upgrade.append(pkg)
                else:
                    pkgs_install.append(pkg) 

            #遍历升级组列表
            for group_name in groups_list:
                pkgs_install += groups_pkgs.get(group_name,[]).get('pkgs_install',[])
                pkgs_upgrade += groups_pkgs.get(group_name,[]).get('pkgs_upgrade',[]) 
            
            return pkgs_install,pkgs_upgrade
        except Exception as e:
            logging.error(str(e))
            return [],[]
    
    def _installed_dependencies(self, pkg_name, all_deps=None):
        """Recursively return all installed dependencies of a given package."""
        # FIXME: Should be part of python-apt, since it makes use of non-public
        #       API. Perhaps by adding a recursive argument to
        #       apt.package.Version.get_dependencies()
        if not all_deps:
            all_deps = set()
        if pkg_name not in self.cache:
            return all_deps
        cur = self.cache[pkg_name]._pkg.current_ver
        if not cur:
            return all_deps
        for sec in ("PreDepends", "Depends", "Recommends"):
            try:
                for dep in cur.depends_list[sec]:
                    dep_name = dep[0].target_pkg.name
                    if dep_name not in all_deps:
                        all_deps.add(dep_name)
                        all_deps |= self._installed_dependencies(dep_name,
                                                                 all_deps)
            except KeyError:
                pass
        return all_deps

    def _split_package_id(self,package):
        """Return the name, the version number and the release of the
        specified package."""
        if "=" in package:
            name, version = package.split("=", 1)
            release = None
        elif "/" in package:
            name, release = package.split("/", 1)
            version = None
        else:
            name = package
            version = release = None
        return name, version, release

    def _emulate_calcul_delete(self,total_pkg):
        logging.info("[DELETE_DETAILS] Start calculating delete dependencies...")
        delete_pkgs = []
        last_delete_count = 0
        cache = Cache()

        with cache.actiongroup():
            if cache.get_changes():
                cache.clear()
            for pkg in total_pkg:
                if '#' in pkg:
                    pkg = pkg.split("#")[0]
                elif '=' in pkg:
                    pkg = pkg.split("=")[0]

                try:
                    cache[pkg].mark_install()
                except Exception as e:
                    logging.error(str(e))
                    break

                if cache.delete_count != last_delete_count:
                    last_delete_count = cache.delete_count
                    for pkg_obj in cache.get_changes():
                        if pkg_obj.marked_delete and pkg_obj.name not in delete_pkgs:
                            delete_pkgs.append(pkg_obj.name)
                            delete_pkgs.append(pkg)
                            logging.info("[DELETE_DETAILS] %s Will be deleted Due to Install or Upgrade %s",pkg_obj.name,pkg)
        
        logging.info("[DELETE_DETAILS] End calculating delete dependencies...")
        cache.close()

    def _self_upgrade_finished(self,upgrade_content):
        channel_config = json_config().getWithDefault("update_channel_upgrade",default = {})

        restart_list = channel_config.get("restart_service_list",[self.window_main.APTD_PKG_NAME,self.window_main.BACKEND_PKG_NAME])
        notify_list = channel_config.get("restart_panel_list",[self.window_main.FRONTEND_PKG_NAME])

        #aptdeamon 升级成功之后的重启
        if self.window_main.APTD_PKG_NAME in upgrade_content and self.window_main.BACKEND_PKG_NAME not in upgrade_content:
            self.dbus_send.make_aptdeamon_restart()

        #前端ui 升级后重启控制面板
        if self.window_main.FRONTEND_PKG_NAME in upgrade_content and self.window_main.FRONTEND_PKG_NAME in notify_list:
            self.dbus_send.UpdateDetectFinishedNext(False,[self.window_main.BACKEND_PKG_NAME],'',\
                                                            get_error_string_from_enum(PRIORITY_UPGRADE_SUCCCESSED),'')
        
        #源管理客户端 升级之后的重启
        if self.window_main.SOURCES_UPDATE_NAME in upgrade_content:
            self.dbus_send.make_properties_restart()
            
        if self.window_main.BACKEND_PKG_NAME in upgrade_content and self.window_main.BACKEND_PKG_NAME in restart_list:
            if self.window_main.FRONTEND_PKG_NAME not in upgrade_content:
                self.window_main.configs_uncover.setValue("SystemStatus","backend_restart",str(True))
                if self.window_main.SOURCES_UPDATE_NAME in upgrade_content:
                    self.window_main.configs_uncover.setValue("SystemStatus","source_restart",str(True))
            
            self.dbus_send.Quit(None)
            return

        if self.window_main.FRONTEND_PKG_NAME in upgrade_content:
            return
        else:
            if self.window_main.SOURCES_UPDATE_NAME in upgrade_content:
                self.window_main.start_update()
            else:
                self.window_main.start_available()

    def run_steps_script(self,progress,final_stage,steps):
        if progress == 50:
            script_list = self.backend_data.get_pre_script(steps)
            for scr in script_list:
                if self.scripts.run_plugin(scr) == False:
                    return True
        elif progress == 100:
            script_list = self.backend_data.get_post_script(final_stage)
            for scr in script_list:
                if self.scripts.run_plugin(scr) == False:
                    return True
        else:
            script_list = self.backend_data.get_post_script(final_stage) + self.backend_data.get_pre_script(steps)
            for scr in script_list:
                if self.scripts.run_plugin(scr) == False:
                    return True
        return True

    def _start_install_queue(self):
        final_stage = False

        now_steps = self.backend_data.steps_queue.pop()
        progress_begin,progress_end = self.backend_data.get_progress(now_steps)
        pkgs_install,pkgs_upgrade,pkgs_remove = self.backend_data.get_resolver_steps(now_steps)

        self.run_steps_script(progress_begin,self.backend_data.last_task,now_steps)

        self.dbus_send.NotifyStepsInstalled(True,self.backend_data.get_mid_notify(now_steps))

        if self.backend_data.check_steps_reboot(now_steps) == True or self.backend_data.steps_queue == []:
            final_stage = True
        
        if self.aptd_base.on_installing == False:
            self.entering_installed()
            self.aptd_base.on_installing = True

        self.backend_data.last_task = now_steps
        logging.info("INSTALL(%s) install:%d , upgrade:%d remove:%d",now_steps,len(pkgs_install),len(pkgs_upgrade),len(pkgs_remove))
        self.commit_install(pkgs_install, pkgs_upgrade, pkgs_remove,progress_begin,progress_end,final_stage)
        
    def _shutdown_exception(self):
        logging.info("Current number of restarts exceeded...")
        self.disable_next_steps()
        self.backend_data.refresh()
        self._release_install_lock()
        self.system_reboot()

    def _action_done(self, action, is_cancelled,success, error_string='',error_desc='',error_code=''):    
        self.window_main.now_working = self.ACTION_DEFUALT_STATUS
        if action == self.ACTION_SLEF_UPGRADE:
            if success:
                upgrade_content = self.now_upgrades.get_content()
                if self.window_main.GROUPS_PKG_NAME in upgrade_content:
                    self.window_main.start_available()
                else:
                    self._self_upgrade_finished(upgrade_content)
            else:
                self.dbus_send.UpdateDetectFinishedNext(False,[''],get_error_code_from_enum(error_code),\
                                get_error_string_from_enum(ERROR_UPDATE_DEFAULT_FAILED),error_string +' '+error_desc)
        elif action == self.ACTION_CHECK_RESOLVER:
            if success == False:
                self.database.insert_info(self.action_mode,self.now_upgrades.single_pkgs,\
                                                            self.now_upgrades.upgrade_groups,[],success,error_string,error_desc)
                if self.action_mode != self.MODE_INSTALL_SYSTEM:
                    self.dbus_send.UpdateDependResloveStatus(success,False,[''],[''],[''],error_string,error_desc)
                else:
                    self.dbus_send.DistupgradeDependResloveStatus(success,False,[''],[''],[''],error_string,error_desc)
            else:
                self.backend_data.write_local()
        elif action == self.ACTION_DOWNLOADONLY:
            if success == True:
                if self.action_mode == self.MODE_INSTALL_PARTIAL:
                    self.backend_data.will_upgrade_content += self.now_upgrades.get_content()
                else:
                    self.backend_data.will_upgrade_content = self.now_upgrades.get_content()
            else:
                self.backend_data.will_upgrade_content = []

            self.dbus_send.UpdateDownloadFinishedNext(success,self.now_upgrades.get_content(),\
                                                            get_error_code_from_enum(error_code),error_string,error_desc)

            if success == False:
                self.database.insert_info(self.action_mode,self.now_upgrades.single_pkgs,\
                                                            self.now_upgrades.upgrade_groups,[],success,error_string,error_desc)

            if is_cancelled == False and self.backend_data.trigger_by_reboot == True and \
                                                        self.delay_shutdown.lock() == True:
                self.delay_shutdown.connect_logind()
        elif action == self.ACTION_INSTALL:
            if success == False and (is_cancelled == True or error_code == ERROR_PACKAGE_DOWNLOAD_FAILED):
                pass
            else:
                self.exit_installed()
                if is_cancelled == False:
                    self.run_steps_script(100,self.backend_data.last_task,'')
                
                if success and self.backend_data.last_task != self.backend_data.resolver_name:
                    self.dbus_send.start_popup_on_installed(True,self.backend_data.get_post_notify())

            self.dbus_send.UpdateInstallFinishedNext(success,self.now_upgrades.get_content(),\
                                                    get_error_code_from_enum(error_code),error_string,error_desc)

            if success == False or self.backend_data.last_task == self.backend_data.resolver_name:
                self.database.insert_info(action,self.now_upgrades.single_pkgs,\
                                                            self.now_upgrades.upgrade_groups,[],success,error_string,error_desc)

            if success:
                self.backend_data.push_pop(self.now_upgrades.get_content())
                self.dbus_send.CheckRebootRequired("self")
                logging.info("Install or Upgrade successful, so Now update Cache...")
                self.window_main.refresh_cache()
        elif action == self.ACTION_INSTALL_SHUTDOWN:
            reboot = True
            self.exit_installed()
            self.plymouth_quit_retain_splash()
            self.run_steps_script(100,self.backend_data.last_task,'')
            
            if success == True:
                if self.backend_data.steps_queue == []:
                    self.backend_data.need_poweroff_install = False
                    self.backend_data.install_finished_status = self.backend_data.SHUTDOWN_INSTALL_SUCCESS
                    self.disable_next_steps()

                    if self.backend_data.finished_status == "shutdown":
                        reboot = False
                else:
                    self.enable_next_steps()
            else:
                self.backend_data.need_poweroff_install = False
                self.backend_data.install_finished_status = self.backend_data.SHUTDOWN_INSTALL_FAILED
                self.disable_next_steps()

                if self.backend_data.finished_status == "shutdown":
                    reboot = False

            if success == False or self.backend_data.last_task == self.backend_data.resolver_name:
                self.database.insert_info(action,self.now_upgrades.single_pkgs,\
                                                            self.now_upgrades.upgrade_groups,[],success,error_string,error_desc)
            self.dbus_send.UpdateInstallFinishedNext(success,self.now_upgrades.get_content(),\
                                                get_error_code_from_enum(error_code),error_string,error_desc)
            
            if self.backend_data.reboot_limity > 0:
                self.backend_data.reboot_limity = self.backend_data.reboot_limity - 1
                logging.info("Current remaining restart times %d...",self.backend_data.reboot_limity)
            else:
                self._shutdown_exception()
            
            self.backend_data.write_local()
            
            if reboot == True:
                self.system_reboot()
            else:
                self.system_shutdown()
        elif action == self.ACTION_UPDATE and self.action_mode == self.MODE_UPDATE_ALL:
            if success == False:
                if ERROR_UPDATE_KEY_SIGNATURES in self.aptd_base.error_details:
                    error_desc = get_error_description_from_enum(ERROR_UPDATE_KEY_SIGNATURES)
                elif ERROR_UPDATE_NET_AUTHENTICATION in self.aptd_base.error_details:
                    error_desc = get_error_description_from_enum(ERROR_UPDATE_NET_AUTHENTICATION)
                elif ERROR_UPDATE_NOTREAD_SOURCES in self.aptd_base.error_details:
                    error_desc = get_error_description_from_enum(ERROR_UPDATE_NOTREAD_SOURCES)
                elif ERROR_UPDATE_INVALID_TIME in self.aptd_base.error_details:
                    error_desc = get_error_description_from_enum(ERROR_UPDATE_INVALID_TIME)
            if success:
                #开始生成列表
                self.window_main.start_available()
            else:
                self.dbus_send.UpdateDetectFinishedNext(False,[''],get_error_code_from_enum(error_code),\
                            get_error_string_from_enum(ERROR_UPDATE_DEFAULT_FAILED),error_string +' '+error_desc)
            self.database.insert_into_display("check_time",get_east_8_time()[0:-4])

        elif action == self.ACTION_UPDATE and self.action_mode == self.MODE_UPDATE_CACHE:
            self.dbus_send.UpdateDetectFinishedNext(success,[''],get_error_code_from_enum(error_code),error_string,error_desc)
        elif action == self.ACTION_FIX_BROKEN:
            self.dbus_send.FixBrokenStatusChanged(True,success,100,'',error_string,error_desc)
            logging.warning("fix broken packages is complete...")
            
            if success:
                logging.info("Fix broken packages is complete to success...")
                self.window_main.start_available()
            else:
                self.dbus_send.UpdateDetectFinishedNext(success,[''],get_error_code_from_enum(ERROR_NOT_FIX_SYSTEM),\
                                            get_error_string_from_enum(ERROR_NOT_FIX_SYSTEM),error_string+' '+error_desc)
                logging.error("fix broken packages is complete to failed...")

        elif action == self.ACTION_FIX_INCOMPLETE:
            if success:
                logging.info("fix incomplete install success.")
                logging.info("Fix broken packages is complete to success...")
                self.window_main.start_available()
            else:
                logging.warning("fix incomplete install failed.")
                self.dbus_send.UpdateDetectFinishedNext(success,[''],get_error_code_from_enum(ERROR_NOT_FIX_SYSTEM),\
                                            get_error_string_from_enum(ERROR_NOT_FIX_SYSTEM),error_string+' '+error_desc)
        elif action == self.ACTION_REMOVE_PACKAGES:
            self._release_install_lock()
            self.dbus_send.PurgePackagesFinished(success,error_string,error_desc)
            
        elif action == self.ACTION_INSTALL_DEB or action == self.ACTION_INSTALL_DEB_RESOLVER:
            self._release_install_lock()
            if success == False and '\r\n: \r\n\r\n' in self.aptd_base.error_details:
                error_string = _("Package validation failed and installation was rejected.")
                error_desc = ''
            if error_code != None and str(error_code) != 'error-dep-resolution-failed':
                if error_desc != '':
                    error_string = ",".join((error_string,error_desc))
                self.dbus_send.InstalldebFinished(success,error_string,'')
                UpdateMsg = {}
                if success:
                    status = 'success'
                    UpdateMsg.update({"errorCode":" "})
                else:
                    status = 'failed'
                    UpdateMsg.update({"errorCode":str(error_string+" "+error_desc)})
                #apt发送数据
                if self.window_main.configs_uncover.getWithDefault("SystemStatus", "upload_installer_log", False) == True:
                    
                    UpdateMsg.update({"appname":str(self.window_main.deb_obj.get("debname","None").split("_")[0])})
                    UpdateMsg.update({"source":str(self.window_main.deb_obj.get("source","kylin-system-updater"))})
                    UpdateMsg.update({"status":str(status)})
                    UpdateMsg.update({"new_version":str(self.window_main.deb_obj.get("debname","None").split("_")[1])})
                    UpdateMsg.update({"old_version":str(self.window_main.deb_obj.get("old_version","None"))})
                    self.collector.Upgrade_Process_Msg(self.action, UpdateMsg.copy())
                    self.window_main.deb_obj = {}
        elif action == self.ACTION_BACKGROUND_UPGRADE:
            self._release_install_lock()
            UpdateMsg = {}
            if success:
                status = 'success'
            else:
                status = 'failed'
            self.collector.Generate_Msg(self.collector.background_list, self.action_mode)
            for bl in self.collector.background_list:
                pkg = self.collector.cache[bl]
                UpdateMsg.update({"appname":str(bl)})
                UpdateMsg.update({"source":"Kylin Background upgrade"})
                UpdateMsg.update({"status":str(status)})
                UpdateMsg.update({"errorCode":str(error_string+" "+error_desc)})
                if self.collector.background_version[bl]:
                    UpdateMsg.update({"old_version":self.collector.background_version[bl]})
                UpdateMsg.update({"new_version":self.collector.cache[bl].candidate.source_version})
                # self.collector.Upgrade_Process_Msg(self.action, UpdateMsg.copy())
            
            self.dbus_send.UpdateInstallFinishedNext(success,self.now_upgrades.get_content(),\
                                                        get_error_code_from_enum(error_code),error_string,error_desc)
    
    def entering_installed(self):
        logging.info("The process is now in the installtion phase")
        self._start_install_lock(_("Kylin System Updater"))

    def exit_installed(self):
        self._release_install_lock()

    def _start_install_lock(self, caller='kylin-system-updater'):
        self.window_main.configs_uncover.setValue("SystemStatus","abnormal_reboot",str(True))
        self.inhibit_shutdown.lock(caller=caller,mode='block')

    def _release_install_lock(self):
        self.window_main.configs_uncover.setValue("SystemStatus","abnormal_reboot",str(False))
        self.inhibit_shutdown.unlock()

    def _message_to_plymouth(self,message):
        subprocess.call(["/bin/plymouth", "message", "--text", message])

    def _progress_to_plymouth(self,progress):
        tmp = ("--progress=%d"%progress)
        run_cmd = ["/bin/plymouth","system-update",tmp]
        subprocess.call(run_cmd)

    def _stop_lightdm_service(self):
        logging.info("Stopping lightdm service")
        args = ["systemctl","stop","lightdm.service"]
        try:
            p = subprocess.run(args,timeout=1,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            if p.returncode == 0:
                logging.info("start stop lightdm.service to success...")
                return True
            else:
                logging.info("start stop lightdm.service to failed...")
                return False
        except Exception as exc:
            logging.error(exc)

    def _switch_to_tty1(self):
        logging.info("Force switch to tty1...")
        args = ["chvt","1"]
        logging.debug(str(args))
        try:
            p = subprocess.run(args,timeout=0.5,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            if p.returncode == 0:
                logging.info("switch to tty1 successly...")
                return True
            else:
                logging.info("switch to tty1 success to failed...")
                return False
        except Exception as exc:
            logging.error(exc)

    def _show_systemd_inhibit(self):
        logging.info("Show systemd inhibit...")
        args = ["systemd-inhibit"]
        try:
            p = subprocess.run(args,timeout=2,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            logging.info(str(p.stdout))
        except Exception as exc:
            logging.error(exc)

    def check_plymouth_state(self):
        try:
            args = ["/bin/plymouth","--ping"]
            p = subprocess.run(args,timeout=2,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            if p.returncode == 0:
                logging.info("Check: Plymouthd Success to Boot...")
            else:
                logging.info("Check: Plymouthd Failed to Boot and Restart boot up again...")
                self.start_plymouth_splash()
        except Exception as exc:
            logging.error(exc) 

    def start_plymouth_splash(self):
        try:
            logging.info("Running plymouth --splash")
            if os.path.exists("/sbin/plymouthd"):
                subprocess.run(["/sbin/plymouthd", "--mode=boot","--attach-to-session"],timeout=1)
            if os.path.exists("/bin/plymouth"):
                subprocess.Popen(["/bin/plymouth", "show-splash","--wait"])
                subprocess.call(["/bin/plymouth","system-update","--progress=0"])
        except Exception as exc:
            logging.error(exc)

    def plymouth_quit_retain_splash(self):
        if os.path.exists("/bin/plymouth"):
            subprocess.run(["/bin/plymouth","quit","--retain-splash"])

    def system_shutdown(self):
        args = ["/sbin/shutdown","-h","now"]
        p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
        if p.returncode == 0:
            logging.info("Now start to shutdown device success...")
            return True
        else:
            logging.info("Now start to shutdown device failed...")
            return False

    def system_reboot(self):
        args = ["/sbin/shutdown","-r","now"]
        p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
        if p.returncode == 0:
            logging.info("Now start to reboot device success...")
            return True
        else:
            logging.info("Now start to reboot device failed...")
            return False
            
    def enable_next_steps(self):
        args = ["systemctl","enable","kylin-upgrade-poweroff.service"]
        p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
        if p.returncode == 0:
            logging.info("start enable kylin-upgrade-poweroff.service to success...")
            return True
        else:
            logging.info("start enable kylin-upgrade-poweroff.service to failed...")
            return False

    def _restart_logind_service(self):
        try:
            args = ["systemctl", "stop","systemd-logind.service"]
            p = subprocess.run(args,timeout=1,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            logging.warning("Restart systemd logind service for shutdown install mode..."+str(p.stdout))
        except Exception as exc:
            logging.error(exc)

    def disable_next_steps(self):
        args = ["systemctl","is-enabled","kylin-upgrade-poweroff.service"]
        p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
        if p.returncode != 0:
            logging.info("kylin-upgrade-poweroff.service is not runing and will be enable...")
        else:
            logging.info("kylin-upgrade-poweroff.service has been enabled and not need to reabled...")
            args = ["systemctl","disable","kylin-upgrade-poweroff.service"]
            p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            if p.returncode == 0:
                logging.info("start disabled kylin-upgrade-poweroff.service to success...")
                return True
            else:
                logging.info("start disabled kylin-upgrade-poweroff.service to failed...")
                return False

    def _update_to_config(self,pkgs_install,pkgs_upgrade,pkgs_remove):
        try:
            output_upgrade = {}

            output_upgrade.update({"pkgs_install":pkgs_install})
            output_upgrade.update({"pkgs_upgrade":pkgs_upgrade})
            output_upgrade.update({"pkgs_remove":pkgs_remove})

            with open(self.INSTALL_OUTPUT_JSON, 'w', encoding='utf-8') as f:
                json.dump(output_upgrade, f, ensure_ascii=False, indent=4)

            logging.info("Generate Jsonfile(%s) to complete... ",self.INSTALL_OUTPUT_JSON)
            logging.info("Update Packages list to config file...")
        except Exception as exc:
            logging.error(exc)
            
    def emulate_calcul_broken(self,pkgs):
        args = ["apt-get", "install","--simulate"]
        args = args + pkgs

        p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
        # logging.error(str(p.stdout))
        return p.stdout

    def start_backup_restore(self):
        obj = self.bus.get_object('com.kylin.backup','/')
        interface = dbus.Interface(obj, dbus_interface='com.kylin.backup.manager')
        logging.info("Now start to restore system...")
        interface.autoRestoreForSystemUpdate("root",0,timeout=0.5)

        self.connet_restore_progress()

    def connet_restore_progress(self):
        def restore_handle_signal(x,progress):
            logging.info("Backup restore now current progress:%d...",progress)
            self._progress_to_plymouth(progress)

        obj = self.bus.get_object('com.kylin.backup','/')
        obj.connect_to_signal(
            "sendRate",
            restore_handle_signal,
            dbus_interface="com.kylin.backup.manager")

    def emulate_update(self):
        args = ["apt-get", "update"]

        p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
        logging.warning(str(p.stdout))

# try aptdaemon
if os.path.exists("/usr/sbin/aptd") \
   and "UPDATE_MANAGER_FORCE_BACKEND_SYNAPTIC" not in os.environ:
    # check if the gtkwidgets are installed as well
    try:
        from .InstallBackendAptdaemon import InstallBackendAptdaemon
    except ImportError:
        logging.exception("importing aptdaemon")

def get_backend(*args, **kwargs):
    """Select and return a package manager backend."""
    # try aptdaemon
    if (os.path.exists("/usr/sbin/aptd")
            and "UPDATE_MANAGER_FORCE_BACKEND_SYNAPTIC" not in os.environ):
        # check if the gtkwidgets are installed as well
        try:
            return InstallBackendAptdaemon(*args, **kwargs)
        except NameError:
            logging.exception("using aptdaemon failed")

    # nothing found, raise
    raise Exception("No working backend found, please try installing "
                    "aptdaemon or synaptic")

class InhibitShutdownLock():
    # 禁止关机锁文件路径
    FILELOCK_PATH = "/tmp/lock/"
    SHUTDOWN_BLOCK_FILELOCK = "kylin-update.lock"
    
    def __init__(self):
        self.inhibit_lock = None
        self.pidfile = None

    #安装时禁止关机 进行加锁
    def lock(self, caller='kylin-system-updater',mode='block'):
        """
        Send a dbus signal to logind to not suspend the system, it will be
        released when the return value drops out of scope
        """
        if self.inhibit_lock == None:
            try:
                from gi.repository import Gio, GLib
                logging.info("Start acquiring Shutdown lock(%s)...",mode)
                connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
                
                var, fdlist = connection.call_with_unix_fd_list_sync(
                    'org.freedesktop.login1', '/org/freedesktop/login1',
                    'org.freedesktop.login1.Manager', 'Inhibit',
                    GLib.Variant('(ssss)',
                                ('shutdown:sleep',
                                caller, _("Installing or removing software packages."),
                                mode)),
                    None, 0, -1, None, None)
                self.inhibit_lock = Gio.UnixInputStream(fd=fdlist.steal_fds()[var[0]])
                self.LockedPreventShutdown()
                logging.info("Shutdown Has been locked...")
                return True
            except Exception as e:
                logging.error(e)
                return False
        else:
            logging.info("No need to acquired lock and Shutdown Has been locked...")
            return False

    #解锁禁止关机 
    def unlock(self):
        try:
            self.unLockedEnableShutdown()
            if self.inhibit_lock != None:
                self.inhibit_lock.close()
                self.inhibit_lock = None
                logging.info("Shutdown Has been unlocked...")
            else:
                logging.info("Not locked and Quitting ...")
        except Exception as e:
            self.inhibit_lock = None
            logging.error("unlock failed." + str(e))

    #安装时禁止关机 进行加锁
    def LockedPreventShutdown(self):
        #不为空是表示以及被锁
        if self.pidfile != None:
            logging.error("self.pidfile file disc not is None,Has been locked...")
            return False

        if not os.path.exists(self.FILELOCK_PATH):
            #不存在创建
            logging.info("File(%s) is not exists and will be create",self.FILELOCK_PATH)
            os.makedirs(self.FILELOCK_PATH)
        else:
            #当目录存在时进行删除 不删除进行创建文件的话会报错 
            # file cannot be locked.[Errno 11] Resource temporarily unavailable
            # 资源被占用报错
            shutil.rmtree(self.FILELOCK_PATH)
            logging.info("File(%s) is exists and will be delete and create",self.FILELOCK_PATH)
            os.makedirs(self.FILELOCK_PATH)        

        try:
            self.pidfile = open(os.path.join(self.FILELOCK_PATH, self.SHUTDOWN_BLOCK_FILELOCK), "w+")
            fcntl.flock(self.pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
            return True
        except Exception as e:
            logging.error("file cannot be locked." + str(e))
            self.pidfile = None
            return False

    #解锁禁止关机 
    def unLockedEnableShutdown(self):
        #未加锁退出
        if not self.pidfile:
            logging.info("Not locked and Quitting ...")
            return False
        try:
            fcntl.flock(self.pidfile, fcntl.LOCK_UN)
            self.pidfile.close()
            self.pidfile = None

            # Fix 修复权限问题 当普通用户无法使用 所以直接删除目录
            if os.path.exists(self.FILELOCK_PATH):
                shutil.rmtree(self.FILELOCK_PATH)
                logging.info('Emptying the lockPath(%s) is complete...',self.FILELOCK_PATH)
            else:
                logging.info("Emptying the lockPath(%s) is Failed...",self.FILELOCK_PATH)

            return True
        except Exception as e:
            logging.error("unlock failed." + str(e))
            self.pidfile = None
            return False   